#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Docker备份工具
支持从远程服务器创建Docker容器备份
"""

import os
import sys
import json
import time
import shutil
import subprocess
import logging
import getpass
import paramiko
import pexpect
from datetime import datetime
from tqdm import tqdm

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('migration.log'),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger(__name__)

class ConfigManager:
    """配置管理器"""
    
    def __init__(self, config_file="config.json"):
        self.config_file = config_file
        self.config = self.load_config()
    
    def load_config(self):
        """加载配置文件"""
        if not os.path.exists(self.config_file):
            print(f"❌ 配置文件 {self.config_file} 不存在")
            return self.create_default_config()
        
        try:
            with open(self.config_file, 'r', encoding='utf-8') as f:
                config = json.load(f)
            print(f"✅ 配置文件加载成功: {self.config_file}")
            return config
        except Exception as e:
            print(f"❌ 配置文件加载失败: {e}")
            return self.create_default_config()
    
    def create_default_config(self):
        """创建默认配置"""
        default_config = {
            "source_server": {
                "host": "43.138.5.146",
                "user": "root",
                "port": 22
            },
            "login": {
                "method": "certificate",
                "certificate_path": "./duijie.pem",
                "password": ""
            },
            "backup": {
                "include_directories": [
                    "/etc",
                    "/home", 
                    "/opt",
                    "/var/www",
                    "/var/spool/cron",
                    "/root",
                    "/var/lib/mysql",
                    "/var/lib/postgresql",
                    "/var/lib/redis",
                    "/usr/local/apps",
                    "/data",
                    "/backup"
                ],
                "exclude_directories": [
                    "/proc",
                    "/sys", 
                    "/dev",
                    "/tmp",
                    "/var/tmp",
                    "/var/cache",
                    "/var/log",
                    "/run",
                    "/mnt",
                    "/media"
                ],
                "database_backup": {
                    "mysql": True,
                    "postgresql": True,
                    "redis": True,
                    "mongodb": False
                }
            },
            "docker": {
                "base_image": "auto",
                "expose_ports": [22, 80, 443, 3306, 6379, 27017],
                "port_mappings": {
                    "22": 2222,
                    "80": 8080,
                    "443": 8443,
                    "3306": 3306,
                    "6379": 6379,
                    "27017": 27017
                },
                "environment": {
                    "TZ": "Asia/Shanghai",
                    "LANG": "en_US.UTF-8"
                }
            }
        }
        
        try:
            with open(self.config_file, 'w', encoding='utf-8') as f:
                json.dump(default_config, f, indent=2, ensure_ascii=False)
            print(f"✅ 默认配置文件已创建: {self.config_file}")
        except Exception as e:
            print(f"❌ 创建配置文件失败: {e}")
        
        return default_config
    
    def save_config(self):
        """保存配置"""
        try:
            with open(self.config_file, 'w', encoding='utf-8') as f:
                json.dump(self.config, f, indent=2, ensure_ascii=False)
            print(f"✅ 配置文件已保存: {self.config_file}")
        except Exception as e:
            print(f"❌ 保存配置文件失败: {e}")
    
    def update_config(self, updates):
        """更新配置"""
        for key, value in updates.items():
            if isinstance(value, dict) and key in self.config:
                self.config[key].update(value)
            else:
                self.config[key] = value
        self.save_config()

class ServerDockerBackupTool:
    def __init__(self, config_manager: ConfigManager):
        # 检测paramiko
        try:
            import paramiko
        except ImportError:
            print("❌ 缺少paramiko库，请安装: pip3 install paramiko")
            raise ImportError("paramiko库未安装")
        
        self.config_manager = config_manager
        self.config = config_manager.config
        
        # 使用固定的backup文件夹
        self.backup_dir = "backup"
        
        # 从配置中获取连接信息
        self.source_host = self.config["source_server"]["host"]
        self.source_port = self.config["source_server"]["port"]
        self.source_user = self.config["source_server"]["user"]
        
        # 登录方式
        self.login_method = self.config["login"]["method"]
        self.password = self.config["login"].get("password", "")
        self.ssh_key_path = self.config["login"].get("certificate_path", "")
        
        # 检查证书权限
        if self.login_method == "certificate" and self.ssh_key_path:
            self.check_certificate_permissions()
        
    def check_certificate_permissions(self):
        """检查证书文件权限"""
        try:
            if not os.path.exists(self.ssh_key_path):
                print(f"❌ 证书文件不存在: {self.ssh_key_path}")
                return False
                
            # 获取当前权限
            current_mode = os.stat(self.ssh_key_path).st_mode & 0o777
            
            # 检查权限是否为600（仅所有者可读写）
            if current_mode != 0o600:
                print(f"⚠️  证书文件权限不正确: {oct(current_mode)}")
                print("🔧 正在修正证书文件权限...")
                
                # 修改权限为600
                os.chmod(self.ssh_key_path, 0o600)
                
                # 验证权限修改
                new_mode = os.stat(self.ssh_key_path).st_mode & 0o777
                if new_mode == 0o600:
                    print("✅ 证书文件权限已修正为600")
                    return True
                else:
                    print(f"❌ 权限修正失败，当前权限: {oct(new_mode)}")
                    return False
            else:
                print("✅ 证书文件权限正确")
                return True
                
        except Exception as e:
            print(f"❌ 检查证书文件权限失败: {e}")
            return False
        
    def get_ssh_options(self):
        """获取SSH选项"""
        options = ["-o", "ConnectTimeout=10", "-o", "StrictHostKeyChecking=no", "-p", str(self.source_port)]
        if self.ssh_key_path and self.login_method == "certificate":
            options.extend(["-i", self.ssh_key_path, "-o", "BatchMode=yes"])
        return options
        
    def execute_ssh_command(self, command: str, timeout: int = 30) -> tuple:
        """使用pexpect执行SSH命令，返回(成功标志, 输出, 错误)"""
        try:
            if self.login_method == "password":
                # 使用pexpect模拟SSH密码登录
                ssh_cmd = f"ssh -p {self.source_port} {self.source_user}@{self.source_host} '{command}'"
                
                # 启动SSH进程
                child = pexpect.spawn(ssh_cmd, timeout=timeout)
                
                # 等待密码提示
                i = child.expect(['password:', 'Password:', 'Are you sure you want to continue connecting', pexpect.EOF, pexpect.TIMEOUT])
                
                if i == 0 or i == 1:  # 密码提示
                    # 输入密码
                    child.sendline(self.password)
                    
                    # 等待命令执行结果
                    i = child.expect([pexpect.EOF, pexpect.TIMEOUT])
                    
                    if i == 0:  # 命令执行完成
                        output = child.before.decode('utf-8', errors='ignore').strip()
                        return True, output, ""
                    else:  # 超时
                        child.close()
                        return False, "", "Command execution timeout"
                        
                elif i == 2:  # 首次连接确认
                    # 确认连接
                    child.sendline('yes')
                    
                    # 等待密码提示
                    i = child.expect(['password:', 'Password:', pexpect.EOF, pexpect.TIMEOUT])
                    
                    if i == 0 or i == 1:  # 密码提示
                        # 输入密码
                        child.sendline(self.password)
                        
                        # 等待命令执行结果
                        i = child.expect([pexpect.EOF, pexpect.TIMEOUT])
                        
                        if i == 0:  # 命令执行完成
                            output = child.before.decode('utf-8', errors='ignore').strip()
                            return True, output, ""
                        else:  # 超时
                            child.close()
                            return False, "", "Command execution timeout"
                    else:
                        child.close()
                        return False, "", "Unexpected response after connection confirmation"
                        
                else:  # EOF或超时
                    child.close()
                    return False, "", "Connection failed or timeout"
                    
            else:
                # 使用pexpect模拟SSH证书登录
                ssh_cmd = f"ssh -i {self.ssh_key_path} -p {self.source_port} {self.source_user}@{self.source_host} '{command}'"
                
                # 启动SSH进程
                child = pexpect.spawn(ssh_cmd, timeout=timeout)
                
                # 等待可能的提示
                i = child.expect(['Are you sure you want to continue connecting', 'Enter passphrase for key', pexpect.EOF, pexpect.TIMEOUT])
                
                if i == 0:  # 首次连接确认
                    # 确认连接
                    child.sendline('yes')
                    
                    # 等待命令执行结果
                    i = child.expect([pexpect.EOF, pexpect.TIMEOUT])
                    
                    if i == 0:  # 命令执行完成
                        output = child.before.decode('utf-8', errors='ignore').strip()
                        return True, output, ""
                    else:  # 超时
                        child.close()
                        return False, "", "Command execution timeout"
                        
                elif i == 1:  # 密钥密码提示
                    child.close()
                    return False, "", "SSH key requires passphrase, please use password login instead"
                    
                elif i == 2:  # EOF - 命令执行完成
                    output = child.before.decode('utf-8', errors='ignore').strip()
                    return True, output, ""
                    
                else:  # 超时
                    child.close()
                    return False, "", "Command execution timeout"
            
        except Exception as e:
            return False, "", str(e)
    
    def build_ssh_command(self, command: str):
        """构建SSH命令，返回(cmd, env) - 保持兼容性，但实际使用paramiko"""
        if self.login_method == "password":
            # 使用 -p 选项直接传递密码，避免 -e 选项的兼容性问题
            cmd = ["sshpass", "-p", self.password, "ssh", "-p", str(self.source_port), f"{self.source_user}@{self.source_host}", command]
            return cmd, os.environ.copy()
        else:
            cmd = ["ssh", "-i", self.ssh_key_path, "-p", str(self.source_port), f"{self.source_user}@{self.source_host}", command]
            return cmd, os.environ.copy()
        
    def test_ssh_connection(self, host: str, user: str) -> bool:
        """测试SSH连接"""
        try:
            print(f"🔍 使用pexpect测试SSH连接: {user}@{host}")
            success, output, error = self.execute_ssh_command("echo 'SSH连接测试成功'", timeout=15)
            
            if success:
                print("✅ SSH连接测试成功")
                return True
            else:
                print(f"SSH连接失败，错误信息: {error}")
                if self.login_method == "certificate":
                    print("💡 证书认证失败，建议：")
                    print("   1. 检查证书文件是否正确")
                    print("   2. 确认服务器上是否配置了对应的公钥")
                    print("   3. 或者尝试使用密码登录")
                return False
        except Exception as e:
            print(f"SSH连接异常: {e}")
            return False
        
    def check_prerequisites(self) -> bool:
        """检查备份前置条件"""
        print("🔍 检查备份前置条件...")
        
        # 检查必要工具
        required_tools = ["ssh", "scp", "tar", "docker"]
        if self.login_method == "password":
            required_tools.append("sshpass")
            
        for tool in required_tools:
            if shutil.which(tool) is None:
                print(f"❌ 缺少必要工具: {tool}")
                if tool == "sshpass":
                    print("💡 请安装sshpass: brew install sshpass (macOS) 或 apt-get install sshpass (Ubuntu)")
                return False
                
        # 检查SSH连接到源服务器
        print("🔗 测试SSH连接...")
        if not self.test_ssh_connection(self.source_host, self.source_user):
            print(f"❌ 无法连接到源服务器 {self.source_host}")
            return False
                
        print("✅ 前置条件检查完成")
        return True
        
    def check_source_system(self) -> bool:
        """检查源服务器系统信息"""
        print("🔍 检查源服务器系统信息...")
        
        try:
            # 检查是否为Linux系统
            cmd, env = self.build_ssh_command("uname -s")
            
            result = subprocess.run(cmd, capture_output=True, text=True, env=env)
            
            if result.returncode == 0:
                os_type = result.stdout.strip()
                if "Linux" in os_type:
                    print(f"✅ 源服务器系统: {os_type}")
                    return True
                else:
                    print(f"❌ 源服务器不是Linux系统: {os_type}")
                    return False
            else:
                print("❌ 无法读取源服务器系统类型")
                return False
                
        except Exception as e:
            print(f"❌ 检查源服务器失败: {e}")
            return False
            
    def collect_source_system_info(self) -> dict:
        """从源服务器收集系统信息"""
        print("📊 收集系统信息...")
        
        system_info = {}
        
        try:
            # 收集基本信息
            info_commands = {
                "hostname": "hostname",
                "kernel": "uname -r",
                "architecture": "uname -m",
                "memory_info": "free -h",
                "disk_info": "df -h",
                "os_release": "cat /etc/os-release 2>/dev/null || cat /etc/redhat-release 2>/dev/null || cat /etc/issue 2>/dev/null || echo 'Unknown'"
            }
            
            for key, cmd in info_commands.items():
                ssh_cmd, env = self.build_ssh_command(cmd)
                
                result = subprocess.run(ssh_cmd, capture_output=True, text=True, env=env)
                if result.returncode == 0 and result.stdout.strip():
                    system_info[key] = result.stdout.strip()
                else:
                    system_info[key] = f"获取失败: {result.stderr}"
            
            # 获取运行中的服务（兼容不同系统）
            service_commands = [
                "systemctl list-units --type=service --state=running 2>/dev/null",
                "service --status-all 2>/dev/null | grep running",
                "ps aux | grep -E '(apache|nginx|mysql|postgres|redis|ssh)' | grep -v grep"
            ]
            
            for cmd in service_commands:
                ssh_cmd, env = self.build_ssh_command(cmd)
                
                result = subprocess.run(ssh_cmd, capture_output=True, text=True, env=env)
                if result.returncode == 0 and result.stdout.strip():
                    if "systemctl" in cmd:
                        services = [line.split()[0] for line in result.stdout.split('\n')[1:] if line.strip()]
                    elif "service" in cmd:
                        services = [line.split()[1] for line in result.stdout.split('\n') if line.strip() and 'running' in line]
                    else:
                        services = [line.split()[10] for line in result.stdout.split('\n') if line.strip()]
                    system_info["running_services"] = services
                    break
            
            print("✅ 系统信息收集完成")
            return system_info
            
        except Exception as e:
            print(f"❌ 收集系统信息失败: {e}")
            return {}
        
    def preview_backup_content(self) -> dict:
        """预览要备份的内容"""
        print("📋 预览备份内容...")
        
        # 先自动检测服务目录并更新配置
        detected_dirs = self.detect_service_directories()
        self.auto_update_backup_config(detected_dirs)
        
        backup_info = {
            "system_services": [],
            "startup_services": [],
            "important_files": [],
            "config_files": [],
            "cron_jobs": [],
            "database_info": {},
            "system_info": {},
            "detected_services": detected_dirs
        }
        
        try:
            # 1. 获取系统服务信息
            print("🔍 收集系统服务信息...")
            service_commands = [
                "systemctl list-units --type=service --state=running --no-pager",
                "systemctl list-unit-files --type=service --state=enabled --no-pager",
                "service --status-all 2>/dev/null | grep -E '(running|enabled)'",
                "chkconfig --list 2>/dev/null | grep -E 'on|enabled'"
            ]
            
            for cmd in service_commands:
                ssh_cmd, env = self.build_ssh_command(cmd)
                
                result = subprocess.run(ssh_cmd, capture_output=True, text=True, timeout=30, env=env)
                if result.returncode == 0 and result.stdout.strip():
                    if "systemctl" in cmd and "running" in cmd:
                        # 解析运行中的服务
                        lines = result.stdout.strip().split('\n')
                        for line in lines[1:]:  # 跳过标题行
                            if line.strip() and not line.startswith('UNIT'):
                                parts = line.split()
                                if len(parts) >= 1:
                                    service_name = parts[0]
                                    if service_name not in backup_info["system_services"]:
                                        backup_info["system_services"].append(service_name)
                    elif "systemctl" in cmd and "enabled" in cmd:
                        # 解析开机启动的服务
                        lines = result.stdout.strip().split('\n')
                        for line in lines[1:]:  # 跳过标题行
                            if line.strip() and not line.startswith('UNIT'):
                                parts = line.split()
                                if len(parts) >= 1:
                                    service_name = parts[0]
                                    if service_name not in backup_info["startup_services"]:
                                        backup_info["startup_services"].append(service_name)
                    break
            
            # 2. 获取重要文件信息
            print("📁 收集重要文件信息...")
            important_paths = [
                "/etc/passwd", "/etc/group", "/etc/shadow", "/etc/gshadow",
                "/etc/hosts", "/etc/resolv.conf", "/etc/fstab", "/etc/crontab",
                "/etc/ssh/sshd_config", "/etc/nginx/nginx.conf", "/etc/apache2/apache2.conf",
                "/etc/mysql/my.cnf", "/etc/postgresql/postgresql.conf", "/etc/redis/redis.conf"
            ]
            
            for path in important_paths:
                ssh_cmd, env = self.build_ssh_command(f"ls -la {path} 2>/dev/null")
                
                result = subprocess.run(ssh_cmd, capture_output=True, text=True, timeout=10, env=env)
                if result.returncode == 0 and result.stdout.strip():
                    backup_info["important_files"].append(path)
            
            # 3. 获取配置中指定的文件
            print("⚙️  收集配置指定的文件...")
            for include_dir in self.config["backup"]["include_directories"]:
                ssh_cmd, env = self.build_ssh_command(f"find {include_dir} -type f -name '*.conf' -o -name '*.cfg' -o -name '*.ini' -o -name '*.json' -o -name '*.xml' 2>/dev/null | head -20")
                
                result = subprocess.run(ssh_cmd, capture_output=True, text=True, timeout=15, env=env)
                if result.returncode == 0 and result.stdout.strip():
                    config_files = result.stdout.strip().split('\n')
                    backup_info["config_files"].extend(config_files[:10])  # 限制数量
            
            # 4. 获取计划任务信息
            print("⏰ 收集计划任务信息...")
            cron_commands = [
                "crontab -l 2>/dev/null",
                "ls -la /etc/cron.d/ 2>/dev/null",
                "ls -la /etc/cron.daily/ 2>/dev/null",
                "ls -la /etc/cron.hourly/ 2>/dev/null",
                "ls -la /etc/cron.monthly/ 2>/dev/null",
                "ls -la /etc/cron.weekly/ 2>/dev/null"
            ]
            
            for cmd in cron_commands:
                ssh_cmd, env = self.build_ssh_command(cmd)
                
                result = subprocess.run(ssh_cmd, capture_output=True, text=True, timeout=10, env=env)
                if result.returncode == 0 and result.stdout.strip():
                    backup_info["cron_jobs"].append(f"{cmd}: {result.stdout.strip()}")
            
            # 5. 获取数据库信息
            print("🗄️  收集数据库信息...")
            if self.config["backup"]["database_backup"]["mysql"]:
                mysql_cmd = "mysql -e 'SHOW DATABASES;' 2>/dev/null | grep -v 'Database\\|information_schema\\|performance_schema\\|mysql\\|sys'"
                ssh_cmd, env = self.build_ssh_command(mysql_cmd)
                
                result = subprocess.run(ssh_cmd, capture_output=True, text=True, timeout=10, env=env)
                if result.returncode == 0 and result.stdout.strip():
                    backup_info["database_info"]["mysql"] = result.stdout.strip().split('\n')
            
            if self.config["backup"]["database_backup"]["postgresql"]:
                pg_cmd = "psql -l 2>/dev/null | grep -v 'template\\|postgres' | awk '{print $1}' | grep -v '^$'"
                ssh_cmd, env = self.build_ssh_command(pg_cmd)
                
                result = subprocess.run(ssh_cmd, capture_output=True, text=True, timeout=10, env=env)
                if result.returncode == 0 and result.stdout.strip():
                    backup_info["database_info"]["postgresql"] = result.stdout.strip().split('\n')
            
            if self.config["backup"]["database_backup"]["redis"]:
                redis_cmd = "redis-cli info keyspace 2>/dev/null | grep db"
                ssh_cmd, env = self.build_ssh_command(redis_cmd)
                
                result = subprocess.run(ssh_cmd, capture_output=True, text=True, timeout=10, env=env)
                if result.returncode == 0 and result.stdout.strip():
                    backup_info["database_info"]["redis"] = result.stdout.strip()
            
            # 6. 获取系统信息
            print("💻 收集系统信息...")
            system_commands = {
                "hostname": "hostname",
                "os_release": "cat /etc/os-release 2>/dev/null || cat /etc/redhat-release 2>/dev/null || cat /etc/issue 2>/dev/null",
                "kernel": "uname -r",
                "architecture": "uname -m",
                "memory": "free -h",
                "disk_usage": "df -h",
                "cpu_info": "lscpu 2>/dev/null || cat /proc/cpuinfo | grep 'model name' | head -1"
            }
            
            for key, cmd in system_commands.items():
                ssh_cmd, env = self.build_ssh_command(cmd)
                
                result = subprocess.run(ssh_cmd, capture_output=True, text=True, timeout=10, env=env)
                if result.returncode == 0 and result.stdout.strip():
                    backup_info["system_info"][key] = result.stdout.strip()
            
            print("✅ 备份内容预览完成")
            return backup_info
            
        except Exception as e:
            print(f"❌ 预览备份内容失败: {e}")
            return backup_info
    
    def print_backup_preview(self, backup_info: dict):
        """打印备份预览信息"""
        print("\n" + "=" * 80)
        print("📋 备份内容预览")
        print("=" * 80)
        
        # 系统信息
        if backup_info["system_info"]:
            print("\n💻 系统信息:")
            for key, value in backup_info["system_info"].items():
                if key == "os_release":
                    if '=' in value:
                        os_name = value.split('=')[-1].strip('"')
                    else:
                        os_name = value
                    print(f"  操作系统: {os_name}")
                elif key == "memory":
                    if '\\n' in value:
                        mem_info = value.split('\\n')[0]
                    else:
                        mem_info = value
                    print(f"  内存信息: {mem_info}")
                elif key == "disk_usage":
                    if '\\n' in value:
                        disk_info = value.split('\\n')[0]
                    else:
                        disk_info = value
                    print(f"  磁盘使用: {disk_info}")
                else:
                    print(f"  {key}: {value}")
        
        # 系统服务
        if backup_info["system_services"]:
            print(f"\n🔧 运行中的系统服务 ({len(backup_info['system_services'])} 个):")
            for service in backup_info["system_services"][:10]:  # 显示前10个
                print(f"  • {service}")
            if len(backup_info["system_services"]) > 10:
                print(f"  ... 还有 {len(backup_info['system_services']) - 10} 个服务")
        
        # 开机启动服务
        if backup_info["startup_services"]:
            print(f"\n🚀 开机启动服务 ({len(backup_info['startup_services'])} 个):")
            for service in backup_info["startup_services"][:10]:  # 显示前10个
                print(f"  • {service}")
            if len(backup_info["startup_services"]) > 10:
                print(f"  ... 还有 {len(backup_info['startup_services']) - 10} 个服务")
        
        # 重要文件
        if backup_info["important_files"]:
            print(f"\n📁 重要系统文件 ({len(backup_info['important_files'])} 个):")
            for file in backup_info["important_files"]:
                print(f"  • {file}")
        
        # 配置文件
        if backup_info["config_files"]:
            print(f"\n⚙️  配置文件 ({len(backup_info['config_files'])} 个):")
            for file in backup_info["config_files"][:15]:  # 显示前15个
                print(f"  • {file}")
            if len(backup_info["config_files"]) > 15:
                print(f"  ... 还有 {len(backup_info['config_files']) - 15} 个配置文件")
        
        # 计划任务
        if backup_info["cron_jobs"]:
            print(f"\n⏰ 计划任务 ({len(backup_info['cron_jobs'])} 项):")
            for job in backup_info["cron_jobs"]:
                print(f"  • {job}")
        
        # 数据库信息
        if backup_info["database_info"]:
            print(f"\n🗄️  数据库信息:")
            for db_type, info in backup_info["database_info"].items():
                if isinstance(info, list):
                    print(f"  • {db_type}: {len(info)} 个数据库")
                    for db in info[:5]:  # 显示前5个
                        print(f"    - {db}")
                    if len(info) > 5:
                        print(f"    ... 还有 {len(info) - 5} 个数据库")
                else:
                    print(f"  • {db_type}: {info}")
        
        # 检测到的服务目录
        if backup_info["detected_services"]:
            print(f"\n🔍 自动检测到的服务目录:")
            for service_type, dirs in backup_info["detected_services"].items():
                if dirs:
                    print(f"  • {service_type.upper()}:")
                    for dir_path in dirs:
                        print(f"    - {dir_path}")
        
        # 备份目录
        print(f"\n📂 备份目录:")
        for include_dir in self.config["backup"]["include_directories"]:
            print(f"  • {include_dir}")
        
        print(f"\n🚫 排除目录:")
        for exclude_dir in self.config["backup"]["exclude_directories"]:
            print(f"  • {exclude_dir}")
        
        print("=" * 80)
        
        # 询问是否继续
        while True:
            choice = input("\n是否继续创建备份? (y/n): ").strip().lower()
            if choice in ['y', 'yes']:
                return True
            elif choice in ['n', 'no']:
                return False
            else:
                print("❌ 请输入 y 或 n")
        
    def check_existing_backup_files(self) -> list:
        """检查远程服务器上是否已有备份文件"""
        print("🔍 检查远程服务器上是否已有备份文件...")
        
        try:
            # 使用兼容的find命令查找远程服务器上的备份文件
            find_cmd = "find /tmp -name 'server_backup_*.tar.gz' -type f -exec ls -la {} \\; 2>/dev/null | sort -k6,7 -r | head -10"
            
            ssh_cmd, env = self.build_ssh_command(find_cmd)
            
            result = subprocess.run(ssh_cmd, capture_output=True, text=True, timeout=30, env=env)
            
            existing_files = []
            if result.returncode == 0 and result.stdout.strip():
                lines = result.stdout.strip().split('\n')
                for line in lines:
                    if line.strip() and 'server_backup_' in line:
                        # 解析ls -la输出格式: -rw-r--r-- 1 user group size date time filename
                        parts = line.split()
                        if len(parts) >= 9:
                            # 提取文件大小
                            size = int(parts[4])
                            # 提取文件名（完整路径）
                            filepath = parts[-1]
                            
                            # 提取日期时间信息
                            date_str = f"{parts[5]} {parts[6]} {parts[7]}"
                            
                            # 转换日期为时间戳（简化处理）
                            from datetime import datetime
                            try:
                                # 尝试解析日期
                                current_year = datetime.now().year
                                date_with_year = f"{date_str} {current_year}"
                                file_time = datetime.strptime(date_with_year, "%b %d %H:%M %Y")
                                file_time_str = file_time.strftime("%Y-%m-%d %H:%M:%S")
                            except:
                                # 如果解析失败，使用当前时间
                                file_time = datetime.now()
                                file_time_str = "未知时间"
                            
                            # 转换文件大小为可读格式
                            if size >= 1024*1024*1024:
                                size_str = f"{size/(1024*1024*1024):.2f} GB"
                            elif size >= 1024*1024:
                                size_str = f"{size/(1024*1024):.2f} MB"
                            else:
                                size_str = f"{size/1024:.2f} KB"
                            
                            existing_files.append({
                                'path': filepath,
                                'size': size,
                                'size_str': size_str,
                                'time': file_time,
                                'time_str': file_time_str
                            })
            
            if existing_files:
                print(f"📦 发现 {len(existing_files)} 个现有备份文件:")
                for i, file_info in enumerate(existing_files, 1):
                    print(f"  {i}. {os.path.basename(file_info['path'])}")
                    print(f"     大小: {file_info['size_str']}")
                    print(f"     时间: {file_info['time_str']}")
                    print(f"     路径: {file_info['path']}")
                    print()
            else:
                print("📭 未发现现有备份文件")
            
            return existing_files
            
        except Exception as e:
            print(f"❌ 检查现有备份文件失败: {e}")
            return []

    def create_source_backup(self) -> str:
        """在源服务器上创建备份"""
        print("📦 创建系统备份...")
        
        # 创建新的备份文件
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        backup_file = f"/tmp/server_backup_{timestamp}.tar.gz"
        
        # 构建备份目录列表
        include_dirs = " ".join(self.config["backup"]["include_directories"])
        exclude_dirs = " ".join([f"--exclude={dir}" for dir in self.config["backup"]["exclude_directories"]])
        
        # 构建数据库备份脚本
        db_backup_script = ""
        if self.config["backup"]["database_backup"]["mysql"]:
            db_backup_script += """
        # MySQL备份
        if command -v mysqldump >/dev/null 2>&1; then
            mkdir -p /tmp/db_backup
            mysqldump --all-databases > /tmp/db_backup/all_databases.sql 2>/dev/null || echo "MySQL备份失败"
            echo "MySQL备份完成"
        fi
        """
        
        if self.config["backup"]["database_backup"]["postgresql"]:
            db_backup_script += """
        # PostgreSQL备份
        if command -v pg_dump >/dev/null 2>&1; then
            mkdir -p /tmp/db_backup
            pg_dumpall > /tmp/db_backup/all_databases.sql 2>/dev/null || echo "PostgreSQL备份失败"
            echo "PostgreSQL备份完成"
        fi
        """
        
        if self.config["backup"]["database_backup"]["redis"]:
            db_backup_script += """
        # Redis备份
        if command -v redis-cli >/dev/null 2>&1; then
            mkdir -p /tmp/db_backup
            redis-cli SAVE >/dev/null 2>&1 || echo "Redis备份失败"
            cp /var/lib/redis/dump.rdb /tmp/db_backup/ 2>/dev/null || echo "Redis dump文件复制失败"
            echo "Redis备份完成"
        fi
        """
        
        if self.config["backup"]["database_backup"]["mongodb"]:
            db_backup_script += """
        # MongoDB备份
        if command -v mongodump >/dev/null 2>&1; then
            mkdir -p /tmp/db_backup
            mongodump --out /tmp/db_backup/mongodb 2>/dev/null || echo "MongoDB备份失败"
            echo "MongoDB备份完成"
        fi
        """
        
        # 在源服务器上创建备份
        backup_script = f"""
        # 创建数据库备份
        echo "开始创建数据库备份..."
        {db_backup_script}
        
        # 创建系统备份
        echo "开始创建系统备份..."
        tar -czf {backup_file} \\
            {exclude_dirs} \\
            --one-file-system \\
            {include_dirs} /tmp/db_backup
        
        echo "备份文件: {backup_file}"
        ls -lh {backup_file}
        
        # 清理临时数据库备份
        rm -rf /tmp/db_backup
        """
        
        try:
            # 使用pexpect执行备份脚本
            success, output, error = self.execute_ssh_command(backup_script, timeout=300)  # 5分钟超时
            if success:
                print(f"✅ 源服务器备份创建成功: {backup_file}")
                return backup_file
            else:
                print(f"❌ 备份创建失败: {error}")
                return None
                
        except Exception as e:
            print(f"❌ 创建备份失败: {e}")
            return None
            
    def transfer_backup_to_local(self, remote_backup_file: str) -> str:
        """将备份文件从源服务器传输到本地，支持断点续传"""
        print("📤 传输备份文件（支持断点续传）...")
        
        os.makedirs(self.backup_dir, exist_ok=True)
        local_backup_file = f"{self.backup_dir}/{os.path.basename(remote_backup_file)}"
        
        # 检查rsync是否可用
        if shutil.which("rsync") is None:
            print("❌ 本地未安装rsync，无法进行断点续传。请先安装rsync。")
            return None
        
        # 检查本地是否已有部分文件
        local_file_exists = os.path.exists(local_backup_file)
        local_file_size = os.path.getsize(local_backup_file) if local_file_exists else 0
        
        if local_file_exists:
            print(f"📁 检测到本地已有文件: {local_backup_file}")
            print(f"📊 本地文件大小: {local_file_size / (1024*1024):.2f} MB")
            
            # 检查远程文件信息
            try:
                success, output, error = self.execute_ssh_command(f"ls -lh {remote_backup_file} 2>/dev/null", timeout=10)
                if success:
                    remote_info = output.strip()
                    print(f"📊 远程文件信息: {remote_info}")
                    
                    # 提取远程文件大小
                    import re
                    size_match = re.search(r'(\d+(?:\.\d+)?[KMG]?)', remote_info)
                    if size_match:
                        remote_size_str = size_match.group(1)
                        print(f"📊 远程文件大小: {remote_size_str}")
                        
                        # 询问是否继续传输
                        while True:
                            choice = input(f"\n检测到本地已有部分文件，是否继续断点续传? (y/n): ").strip().lower()
                            if choice in ['y', 'yes']:
                                print("🔄 开始断点续传...")
                                break
                            elif choice in ['n', 'no']:
                                # 删除本地部分文件，重新开始
                                os.remove(local_backup_file)
                                print("🗑️  已删除本地部分文件，将重新开始传输")
                                break
                            else:
                                print("❌ 请输入 y 或 n")
                else:
                    print("⚠️  无法获取远程文件信息，将重新开始传输")
                    if local_file_exists:
                        os.remove(local_backup_file)
            except Exception as e:
                print(f"⚠️  检查远程文件失败: {e}，将重新开始传输")
                if local_file_exists:
                    os.remove(local_backup_file)
        
        try:
            if self.login_method == "password":
                password = self.password or getpass.getpass("请输入服务器密码: ")
                env = os.environ.copy()
                env['SSHPASS'] = password
                rsync_cmd = [
                    "sshpass", "-e", "rsync", "-avzP", "--partial", "--inplace",
                    "-e", f"ssh -p {self.source_port}",
                    f"{self.source_user}@{self.source_host}:{remote_backup_file}",
                    local_backup_file
                ]
            else:
                env = os.environ.copy()
                rsync_cmd = [
                    "rsync", "-avzP", "--partial", "--inplace",
                    "-e", f"ssh -i {self.ssh_key_path} -p {self.source_port}",
                    f"{self.source_user}@{self.source_host}:{remote_backup_file}",
                    local_backup_file
                ]
            
            print(f"🔄 运行rsync命令: {' '.join(rsync_cmd)}")
            result = subprocess.run(rsync_cmd, env=env)
            if result.returncode == 0:
                # 验证传输结果
                if os.path.exists(local_backup_file):
                    final_size = os.path.getsize(local_backup_file)
                    print(f"✅ 备份文件传输成功: {local_backup_file}")
                    print(f"📊 最终文件大小: {final_size / (1024*1024):.2f} MB")
                    return local_backup_file
                else:
                    print("❌ 传输完成但本地文件不存在")
                    return None
            else:
                print(f"❌ 备份文件传输失败，rsync返回码: {result.returncode}")
                return None
        except Exception as e:
            print(f"❌ 备份文件传输失败: {e}")
            return None
            
    def detect_base_image(self, system_info: dict) -> str:
        """检测适合的基础Docker镜像"""
        if self.config["docker"]["base_image"] != "auto":
            return self.config["docker"]["base_image"]
            
        os_release = system_info.get('os_release', '').lower()
        
        if 'centos' in os_release or 'redhat' in os_release:
            if '6' in os_release:
                return 'centos:6'
            elif '7' in os_release:
                return 'centos:7'
            elif '8' in os_release:
                return 'centos:8'
            else:
                return 'centos:7'
        elif 'ubuntu' in os_release:
            if '20' in os_release:
                return 'ubuntu:20.04'
            elif '18' in os_release:
                return 'ubuntu:18.04'
            else:
                return 'ubuntu:20.04'
        elif 'debian' in os_release:
            if '11' in os_release:
                return 'debian:11'
            elif '10' in os_release:
                return 'debian:10'
            else:
                return 'debian:11'
        else:
            return 'ubuntu:20.04'  # 默认使用Ubuntu
            
    def generate_dockerfile(self, system_info: dict) -> str:
        """生成Dockerfile用于Docker化备份"""
        print("🐳 生成Dockerfile...")
        
        base_image = self.detect_base_image(system_info)
        
        # 使用固定的docker_backup目录
        docker_dir = f"{self.backup_dir}/docker_backup"
        os.makedirs(docker_dir, exist_ok=True)
        
        # 解压备份文件
        backup_file = f"{self.backup_dir}/server_backup_*.tar.gz"
        import glob
        backup_files = glob.glob(backup_file)
        if backup_files:
            backup_file = backup_files[0]
            print(f"📦 解压备份文件: {backup_file}")
            with tqdm(total=100, desc="解压进度", unit="%") as pbar:
                subprocess.run(["tar", "-xzf", backup_file, "-C", docker_dir], check=True)
                pbar.update(100)
        
        # 构建环境变量
        env_vars = ""
        for key, value in self.config["docker"]["environment"].items():
            env_vars += f'ENV {key}={value}\n'
        
        # 构建端口暴露
        expose_ports = " ".join([str(port) for port in self.config["docker"]["expose_ports"]])
        
        # 构建端口映射字符串
        port_mappings = []
        for container_port, host_port in self.config["docker"]["port_mappings"].items():
            port_mappings.append(f"-p {host_port}:{container_port}")
        port_mapping_str = " ".join(port_mappings)
        
        # 生成镜像名称
        image_name = f"server-backup-{system_info.get('hostname', 'unknown')}"
        
        dockerfile_content = f"""# 服务器Docker化备份 Dockerfile
# 生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
# 源主机: {system_info.get('hostname', 'unknown')}
# 检测到的基础镜像: {base_image}

FROM {base_image}

# 设置环境变量
{env_vars}
# 安装基础工具
RUN apt-get update -y || yum update -y || true
RUN apt-get install -y \\
        wget \\
        curl \\
        vim \\
        net-tools \\
        openssh-server \\
        openssh-client \\
        rsync \\
        tar \\
        gzip \\
        unzip \\
        git \\
        python3 \\
        python3-pip \\
        nginx \\
        mysql-server \\
        redis-server \\
        || yum install -y \\
        wget \\
        curl \\
        vim \\
        net-tools \\
        openssh-server \\
        openssh-clients \\
        rsync \\
        tar \\
        gzip \\
        unzip \\
        git \\
        python3 \\
        python3-pip \\
        nginx \\
        mysql-server \\
        redis \\
        || true

# 创建必要的目录
RUN mkdir -p /var/www /opt/apps /home/backup_user

# 复制系统配置文件（如果存在）
COPY etc/ /etc/ 2>/dev/null || true
COPY home/ /home/ 2>/dev/null || true
COPY opt/ /opt/ 2>/dev/null || true
COPY var/www/ /var/www/ 2>/dev/null || true

# 设置权限
RUN chmod 600 /etc/ssh/sshd_config 2>/dev/null || true
RUN chmod 644 /etc/passwd /etc/group 2>/dev/null || true
RUN chmod 400 /etc/shadow /etc/gshadow 2>/dev/null || true

# 配置SSH服务
RUN ssh-keygen -A || true
RUN echo 'root:password' | chpasswd || true

# 暴露端口
EXPOSE {expose_ports}

# 启动脚本
COPY start.sh /start.sh
RUN chmod +x /start.sh

CMD ["/start.sh"]
"""
        
        # 创建启动脚本
        start_script = """#!/bin/bash
# 启动SSH服务
/usr/sbin/sshd || service ssh start || systemctl start sshd

# 恢复数据库
echo "开始恢复数据库..."

# MySQL恢复
if [ -f /tmp/db_backup/all_databases.sql ] && command -v mysql >/dev/null 2>&1; then
    echo "恢复MySQL数据库..."
    mysql < /tmp/db_backup/all_databases.sql 2>/dev/null || echo "MySQL恢复失败"
fi

# PostgreSQL恢复
if [ -f /tmp/db_backup/all_databases.sql ] && command -v psql >/dev/null 2>&1; then
    echo "恢复PostgreSQL数据库..."
    psql < /tmp/db_backup/all_databases.sql 2>/dev/null || echo "PostgreSQL恢复失败"
fi

# Redis恢复
if [ -f /tmp/db_backup/dump.rdb ] && command -v redis-server >/dev/null 2>&1; then
    echo "恢复Redis数据..."
    cp /tmp/db_backup/dump.rdb /var/lib/redis/ 2>/dev/null || echo "Redis数据恢复失败"
fi

# MongoDB恢复
if [ -d /tmp/db_backup/mongodb ] && command -v mongorestore >/dev/null 2>&1; then
    echo "恢复MongoDB数据..."
    mongorestore /tmp/db_backup/mongodb 2>/dev/null || echo "MongoDB恢复失败"
fi

# 启动MySQL服务
if [ -f /etc/init.d/mysql ]; then
    /etc/init.d/mysql start
elif [ -f /etc/init.d/mysqld ]; then
    /etc/init.d/mysqld start
elif command -v systemctl >/dev/null 2>&1; then
    systemctl start mysql || systemctl start mysqld
fi

# 启动Redis服务
if [ -f /etc/init.d/redis ]; then
    /etc/init.d/redis start
elif [ -f /etc/init.d/redis-server ]; then
    /etc/init.d/redis-server start
elif command -v systemctl >/dev/null 2>&1; then
    systemctl start redis || systemctl start redis-server
fi

# 启动Nginx服务
if [ -f /etc/init.d/nginx ]; then
    /etc/init.d/nginx start
elif command -v systemctl >/dev/null 2>&1; then
    systemctl start nginx
fi

# 启动root目录下的应用
echo "启动root目录下的应用..."
if [ -d /root ]; then
    # 查找并启动常见的应用
    for app in /root/*; do
        if [ -f "$app/start.sh" ]; then
            echo "启动应用: $app"
            cd "$app" && ./start.sh &
        elif [ -f "$app/package.json" ]; then
            echo "启动Node.js应用: $app"
            cd "$app" && npm start &
        elif [ -f "$app/requirements.txt" ]; then
            echo "启动Python应用: $app"
            cd "$app" && python3 app.py &
        elif [ -f "$app/pom.xml" ]; then
            echo "启动Java应用: $app"
            cd "$app" && mvn spring-boot:run &
        fi
    done
fi

echo "所有服务启动完成"
# 保持容器运行
tail -f /dev/null
"""
        
        # 保存文件
        with open(f"{docker_dir}/Dockerfile", "w") as f:
            f.write(dockerfile_content)
            
        with open(f"{docker_dir}/start.sh", "w") as f:
            f.write(start_script)
            
        print(f"✅ Dockerfile已生成: {docker_dir}/Dockerfile")
        print(f"✅ 启动脚本已生成: {docker_dir}/start.sh")
        
        return dockerfile_content
        
    def dockerize_backup(self, system_info: dict) -> bool:
        """Docker化备份"""
        print("🐳 开始Docker化备份...")
        
        try:
            # 生成Dockerfile
            dockerfile_content = self.generate_dockerfile(system_info)
            
            # 创建Docker镜像
            image_name = f"server-backup-{system_info.get('hostname', 'unknown')}:latest"
            
            # 构建Docker镜像
            print("🔨 构建Docker镜像...")
            build_cmd = ["docker", "build", "-t", image_name, "-"]
            
            with tqdm(total=100, desc="构建进度", unit="%") as pbar:
                result = subprocess.run(build_cmd, input=dockerfile_content, capture_output=True, text=True, env=os.environ)
                if result.returncode == 0:
                    pbar.update(100)
                else:
                    print(f"❌ Docker构建失败: {result.stderr}")
                    return False
            
            # 保存镜像
            print("💾 保存Docker镜像...")
            image_file = f"{self.backup_dir}/server_backup_image.tar"
            save_cmd = ["docker", "save", "-o", image_file, image_name]
            
            with tqdm(total=100, desc="保存进度", unit="%") as pbar:
                result = subprocess.run(save_cmd, capture_output=True, text=True, env=os.environ)
                if result.returncode == 0:
                    pbar.update(100)
                else:
                    print(f"❌ Docker镜像保存失败: {result.stderr}")
                    return False
            
            print("✅ Docker化备份完成")
            print(f"📁 Docker镜像已保存: {image_file}")
            print(f"🚀 可以使用以下命令运行容器:")
            
            # 使用配置的端口映射
            port_mappings = []
            for container_port, host_port in self.config["docker"]["port_mappings"].items():
                port_mappings.append(f"-p {host_port}:{container_port}")
            port_mapping_str = " ".join(port_mappings)
            
            print(f"docker run -d {port_mapping_str} {image_name}")
            print(f"\n📋 端口映射:")
            for container_port, host_port in self.config["docker"]["port_mappings"].items():
                print(f"  容器端口 {container_port} -> 主机端口 {host_port}")
            
            return True
            
        except subprocess.CalledProcessError as e:
            print(f"❌ Docker化备份失败: {e}")
            return False
            
    def full_backup(self) -> bool:
        """执行完整Docker化备份"""
        print("🚀 开始执行Docker化备份...")
        
        try:
            # 检查前置条件
            if not self.check_prerequisites():
                return False
                
            # 检查源服务器系统
            if not self.check_source_system():
                return False
            
            # 连接成功后立即检查现有备份文件
            print("🔍 检查远程服务器上是否已有备份文件...")
            existing_files = self.check_existing_backup_files()
            
            # 如果发现现有备份文件，让用户选择
            if existing_files:
                print("\n🤔 发现现有备份文件，请选择操作:")
                print("1. 使用最新的现有备份文件")
                print("2. 使用指定的现有备份文件")
                print("3. 创建新的备份文件")
                print("4. 查看所有现有备份文件详细信息")
                
                while True:
                    choice = input("\n请选择 (1-4): ").strip()
                    
                    if choice == "1":
                        # 使用最新的备份文件
                        latest_file = existing_files[0]  # 已经按时间排序
                        print(f"✅ 选择使用最新备份文件: {latest_file['path']}")
                        # 直接传输现有文件
                        local_backup = self.transfer_backup_to_local(latest_file['path'])
                        if local_backup:
                            # 收集系统信息用于Docker化
                            system_info = self.collect_source_system_info()
                            return self.dockerize_backup(system_info)
                        return False
                        
                    elif choice == "2":
                        # 使用指定的备份文件
                        if len(existing_files) == 1:
                            selected_file = existing_files[0]
                            print(f"✅ 选择使用备份文件: {selected_file['path']}")
                            local_backup = self.transfer_backup_to_local(selected_file['path'])
                            if local_backup:
                                system_info = self.collect_source_system_info()
                                return self.dockerize_backup(system_info)
                            return False
                        else:
                            print(f"\n请选择要使用的备份文件 (1-{len(existing_files)}):")
                            for i, file_info in enumerate(existing_files, 1):
                                print(f"  {i}. {os.path.basename(file_info['path'])} ({file_info['size_str']}, {file_info['time_str']})")
                            
                            while True:
                                try:
                                    file_choice = int(input(f"\n请输入文件编号 (1-{len(existing_files)}): ").strip())
                                    if 1 <= file_choice <= len(existing_files):
                                        selected_file = existing_files[file_choice - 1]
                                        print(f"✅ 选择使用备份文件: {selected_file['path']}")
                                        local_backup = self.transfer_backup_to_local(selected_file['path'])
                                        if local_backup:
                                            system_info = self.collect_source_system_info()
                                            return self.dockerize_backup(system_info)
                                        return False
                                    else:
                                        print(f"❌ 请输入 1-{len(existing_files)} 之间的数字")
                                except ValueError:
                                    print("❌ 请输入有效的数字")
                        
                    elif choice == "3":
                        # 创建新的备份文件
                        print("🆕 将创建新的备份文件...")
                        break
                        
                    elif choice == "4":
                        # 显示详细信息
                        print("\n📋 所有现有备份文件详细信息:")
                        for i, file_info in enumerate(existing_files, 1):
                            print(f"\n{i}. 文件名: {os.path.basename(file_info['path'])}")
                            print(f"   完整路径: {file_info['path']}")
                            print(f"   文件大小: {file_info['size_str']} ({file_info['size']} 字节)")
                            print(f"   创建时间: {file_info['time_str']}")
                            
                            # 检查文件完整性
                            try:
                                if self.login_method == "password" and self.password:
                                    check_cmd = ["sshpass", "-p", self.password, "ssh"] + self.get_ssh_options() + [f"{self.source_user}@{self.source_host}", f"tar -tzf {file_info['path']} >/dev/null 2>&1 && echo 'OK' || echo 'CORRUPTED'"]
                                else:
                                    check_cmd = ["ssh"] + self.get_ssh_options() + [f"{self.source_user}@{self.source_host}", f"tar -tzf {file_info['path']} >/dev/null 2>&1 && echo 'OK' || echo 'CORRUPTED'"]
                                
                                check_result = subprocess.run(check_cmd, capture_output=True, text=True, timeout=30, env=os.environ)
                                if check_result.returncode == 0:
                                    status = "✅ 文件完整" if "OK" in check_result.stdout else "❌ 文件损坏"
                                    print(f"   文件状态: {status}")
                                else:
                                    print(f"   文件状态: ⚠️  无法检查")
                            except:
                                print(f"   文件状态: ⚠️  检查失败")
                        
                        print("\n请重新选择操作 (1-3):")
                        continue
                        
                    else:
                        print("❌ 请输入 1-4 之间的数字")
                
            # 收集源服务器系统信息
            system_info = self.collect_source_system_info()
            
            # 预览备份内容
            backup_info = self.preview_backup_content()
            if not self.print_backup_preview(backup_info):
                print("❌ 用户取消备份")
                return False
            
            # 保存系统信息
            info_file = f"{self.backup_dir}/system_info.json"
            os.makedirs(self.backup_dir, exist_ok=True)
            with open(info_file, "w", encoding="utf-8") as f:
                json.dump(system_info, f, indent=2, ensure_ascii=False)
                
            # 创建新的备份文件
            backup_file = self.create_source_backup()
            if backup_file:
                local_backup = self.transfer_backup_to_local(backup_file)
                if local_backup:
                    # Docker化备份
                    return self.dockerize_backup(system_info)
                    
            return False
            
        except Exception as e:
            print(f"❌ 备份过程中发生错误: {e}")
            return False
            
    def create_backup_report(self, system_info: dict) -> str:
        """创建备份报告"""
        report_file = f"{self.backup_dir}/backup_report.md"
        
        report_content = f"""# 服务器Docker化备份报告

## 备份信息
- 备份时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
- 源服务器: {self.source_host}:{self.source_port}
- 源用户: {self.source_user}
- 备份类型: Docker化备份
- 登录方式: {self.login_method}

## 源服务器信息
- 主机名: {system_info.get('hostname', '未知')}
- 操作系统: {system_info.get('os_release', '未知')}
- 内核版本: {system_info.get('kernel', '未知')}
- 架构: {system_info.get('architecture', '未知')}

## 备份配置
- 包含目录: {', '.join(self.config['backup']['include_directories'])}
- 排除目录: {', '.join(self.config['backup']['exclude_directories'])}
- 数据库备份: {', '.join([k for k, v in self.config['backup']['database_backup'].items() if v])}

## Docker配置
- 基础镜像: {self.config['docker']['base_image']}
- 容器暴露端口: {', '.join(map(str, self.config['docker']['expose_ports']))}
- 端口映射:
{chr(10).join([f'  - 容器端口 {container_port} -> 主机端口 {host_port}' for container_port, host_port in self.config['docker']['port_mappings'].items()])}

## 备份步骤
1. 源服务器系统信息收集
2. 系统备份创建
3. 备份文件传输
4. Docker镜像生成

## 使用说明
1. 备份文件位置: {self.backup_dir}
2. Docker镜像文件: {self.backup_dir}/docker_backup/server_backup_image.tar
3. 运行容器命令: docker run -d {' '.join([f'-p {host_port}:{container_port}' for container_port, host_port in self.config['docker']['port_mappings'].items()])} server-backup-{system_info.get('hostname', 'unknown')}:latest

## 注意事项
- 备份文件包含敏感信息，请妥善保管
- 建议加密备份文件
- 定期清理旧的备份文件

## 故障排除
如果备份失败，请检查：
1. 网络连接是否正常
2. SSH密钥或密码是否正确
3. Docker服务是否运行
4. 磁盘空间是否充足

## 联系信息
如有问题，请联系系统管理员。
"""
        
        with open(report_file, "w", encoding="utf-8") as f:
            f.write(report_content)
            
        print(f"📄 备份报告已生成: {report_file}")
        return report_file

    def detect_service_directories(self) -> dict:
        """检测服务器上的服务目录"""
        print("🔍 自动检测服务目录...")
        
        detected_dirs = {
            "nginx": [],
            "tomcat": [],
            "apache": [],
            "mysql": [],
            "postgresql": [],
            "redis": [],
            "other_services": []
        }
        
        try:
            # 1. 检测Nginx相关目录
            print("🌐 检测Nginx相关目录...")
            nginx_paths = [
                "/etc/nginx", "/usr/local/nginx", "/opt/nginx", "/var/nginx",
                "/var/www", "/var/www/html", "/usr/share/nginx", "/srv/www",
                "/etc/nginx/conf.d", "/etc/nginx/sites-available", "/etc/nginx/sites-enabled"
            ]
            
            for path in nginx_paths:
                success, output, error = self.execute_ssh_command(f"test -d {path} && echo 'exists'", timeout=10)
                if success and "exists" in output:
                    detected_dirs["nginx"].append(path)
            
            # 2. 检测Tomcat相关目录
            print("🐱 检测Tomcat相关目录...")
            tomcat_paths = [
                "/usr/local/tomcat", "/opt/tomcat", "/var/tomcat", "/usr/share/tomcat",
                "/etc/tomcat", "/var/lib/tomcat", "/var/log/tomcat", "/var/cache/tomcat",
                "/usr/local/apache-tomcat*", "/opt/apache-tomcat*"
            ]
            
            for path in tomcat_paths:
                success, output, error = self.execute_ssh_command(f"test -d {path} && echo 'exists'", timeout=10)
                if success and "exists" in output:
                    detected_dirs["tomcat"].append(path)
            
            # 3. 检测Apache相关目录
            print("🕷️ 检测Apache相关目录...")
            apache_paths = [
                "/etc/apache2", "/etc/httpd", "/usr/local/apache2", "/opt/apache2",
                "/var/www", "/var/www/html", "/srv/www", "/usr/share/apache2"
            ]
            
            for path in apache_paths:
                success, output, error = self.execute_ssh_command(f"test -d {path} && echo 'exists'", timeout=10)
                if success and "exists" in output:
                    detected_dirs["apache"].append(path)
            
            # 4. 检测数据库相关目录
            print("🗄️ 检测数据库相关目录...")
            mysql_paths = [
                "/var/lib/mysql", "/etc/mysql", "/usr/local/mysql", "/opt/mysql",
                "/var/log/mysql", "/etc/my.cnf", "/etc/mysql/my.cnf"
            ]
            
            for path in mysql_paths:
                success, output, error = self.execute_ssh_command(f"test -e {path} && echo 'exists'", timeout=10)
                if success and "exists" in output:
                    detected_dirs["mysql"].append(path)
            
            postgresql_paths = [
                "/var/lib/postgresql", "/etc/postgresql", "/usr/local/pgsql", "/opt/postgresql",
                "/var/log/postgresql", "/etc/postgresql/postgresql.conf"
            ]
            
            for path in postgresql_paths:
                success, output, error = self.execute_ssh_command(f"test -e {path} && echo 'exists'", timeout=10)
                if success and "exists" in output:
                    detected_dirs["postgresql"].append(path)
            
            redis_paths = [
                "/var/lib/redis", "/etc/redis", "/usr/local/redis", "/opt/redis",
                "/var/log/redis", "/etc/redis/redis.conf"
            ]
            
            for path in redis_paths:
                success, output, error = self.execute_ssh_command(f"test -e {path} && echo 'exists'", timeout=10)
                if success and "exists" in output:
                    detected_dirs["redis"].append(path)
            
            # 5. 检测其他常见服务目录
            print("🔧 检测其他服务目录...")
            other_paths = [
                "/usr/local/apps", "/opt/apps", "/var/apps", "/home/apps",
                "/usr/local/bin", "/opt/bin", "/var/log", "/var/cache",
                "/usr/local/java", "/opt/java", "/usr/lib/jvm",
                "/usr/local/node", "/opt/node", "/usr/local/npm",
                "/usr/local/python", "/opt/python", "/usr/local/pip",
                "/usr/local/ruby", "/opt/ruby", "/usr/local/gem",
                "/usr/local/php", "/opt/php", "/usr/local/composer",
                "/usr/local/go", "/opt/go", "/usr/local/gopath",
                "/usr/local/docker", "/opt/docker", "/var/lib/docker",
                "/usr/local/k8s", "/opt/k8s", "/etc/kubernetes",
                "/usr/local/jenkins", "/opt/jenkins", "/var/lib/jenkins",
                "/usr/local/gitlab", "/opt/gitlab", "/var/opt/gitlab",
                "/usr/local/sonar", "/opt/sonar", "/opt/sonarqube",
                "/usr/local/nexus", "/opt/nexus", "/var/lib/nexus",
                "/usr/local/artifactory", "/opt/artifactory", "/var/opt/jfrog",
                "/usr/local/elastic", "/opt/elastic", "/var/lib/elasticsearch",
                "/usr/local/kafka", "/opt/kafka", "/var/lib/kafka",
                "/usr/local/zookeeper", "/opt/zookeeper", "/var/lib/zookeeper",
                "/usr/local/rabbitmq", "/opt/rabbitmq", "/var/lib/rabbitmq",
                "/usr/local/activemq", "/opt/activemq", "/var/lib/activemq",
                "/usr/local/mongodb", "/opt/mongodb", "/var/lib/mongodb",
                "/usr/local/cassandra", "/opt/cassandra", "/var/lib/cassandra",
                "/usr/local/influxdb", "/opt/influxdb", "/var/lib/influxdb",
                "/usr/local/prometheus", "/opt/prometheus", "/var/lib/prometheus",
                "/usr/local/grafana", "/opt/grafana", "/var/lib/grafana",
                "/usr/local/kibana", "/opt/kibana", "/var/lib/kibana",
                "/usr/local/logstash", "/opt/logstash", "/var/lib/logstash",
                "/usr/local/filebeat", "/opt/filebeat", "/var/lib/filebeat",
                "/usr/local/metricbeat", "/opt/metricbeat", "/var/lib/metricbeat",
                "/usr/local/heartbeat", "/opt/heartbeat", "/var/lib/heartbeat",
                "/usr/local/packetbeat", "/opt/packetbeat", "/var/lib/packetbeat",
                "/usr/local/auditbeat", "/opt/auditbeat", "/var/lib/auditbeat",
                "/usr/local/journalbeat", "/opt/journalbeat", "/var/lib/journalbeat",
                "/usr/local/functionbeat", "/opt/functionbeat", "/var/lib/functionbeat",
                "/usr/local/winlogbeat", "/opt/winlogbeat", "/var/lib/winlogbeat"
            ]
            
            for path in other_paths:
                success, output, error = self.execute_ssh_command(f"test -d {path} && echo 'exists'", timeout=10)
                if success and "exists" in output:
                    detected_dirs["other_services"].append(path)
            
            print("✅ 服务目录检测完成")
            return detected_dirs
            
        except Exception as e:
            print(f"❌ 检测服务目录失败: {e}")
            return detected_dirs
    
    def auto_update_backup_config(self, detected_dirs: dict):
        """自动更新备份配置"""
        print("⚙️  自动更新备份配置...")
        
        updated = False
        current_include_dirs = set(self.config["backup"]["include_directories"])
        
        # 添加检测到的目录到配置中
        for service_type, dirs in detected_dirs.items():
            for dir_path in dirs:
                if dir_path not in current_include_dirs:
                    self.config["backup"]["include_directories"].append(dir_path)
                    current_include_dirs.add(dir_path)
                    updated = True
                    print(f"  ➕ 添加 {service_type} 目录: {dir_path}")
        
        if updated:
            # 保存更新后的配置
            self.config_manager.save_config()
            print("✅ 备份配置已自动更新")
        else:
            print("ℹ️  无需更新备份配置")
        
        return updated

def main():
    """主函数"""
    print("=" * 60)
    print("Docker备份工具")
    print("=" * 60)
    
    # 加载配置
    config_manager = ConfigManager()
    
    # 显示当前配置
    print(f"\n📋 当前配置:")
    print(f"源服务器: {config_manager.config['source_server']['host']}:{config_manager.config['source_server']['port']}")
    print(f"用户名: {config_manager.config['source_server']['user']}")
    print(f"登录方式: {config_manager.config['login']['method']}")
    
    # 询问是否修改配置
    modify_config = input("\n是否修改配置? (y/n, 默认: n): ").strip().lower()
    
    if modify_config in ['y', 'yes']:
        print("\n🔧 配置修改:")
        
        # 修改源服务器信息
        new_host = input(f"源服务器地址 (当前: {config_manager.config['source_server']['host']}): ").strip()
        if new_host:
            config_manager.config['source_server']['host'] = new_host
            
        new_user = input(f"用户名 (当前: {config_manager.config['source_server']['user']}): ").strip()
        if new_user:
            config_manager.config['source_server']['user'] = new_user
            
        new_port = input(f"端口 (当前: {config_manager.config['source_server']['port']}): ").strip()
        if new_port and new_port.isdigit():
            config_manager.config['source_server']['port'] = int(new_port)
        
        # 修改登录方式
        print("\n🔐 登录方式:")
        print("1. 证书登录")
        print("2. 密码登录")
        login_choice = input("请选择登录方式 (1-2): ").strip()
        
        if login_choice == "2":
            config_manager.config['login']['method'] = "password"
            password = getpass.getpass("请输入服务器密码: ")
            config_manager.config['login']['password'] = password
        else:
            config_manager.config['login']['method'] = "certificate"
            cert_path = input(f"证书路径 (当前: {config_manager.config['login']['certificate_path']}): ").strip()
            if cert_path:
                config_manager.config['login']['certificate_path'] = cert_path
        
        # 修改端口映射
        print("\n🔌 端口映射配置:")
        print("当前端口映射:")
        for container_port, host_port in config_manager.config['docker']['port_mappings'].items():
            print(f"  容器端口 {container_port} -> 主机端口 {host_port}")
        
        modify_ports = input("\n是否修改端口映射? (y/n, 默认: n): ").strip().lower()
        if modify_ports in ['y', 'yes']:
            print("输入新的主机端口 (留空保持当前值):")
            for container_port in config_manager.config['docker']['port_mappings'].keys():
                current_host_port = config_manager.config['docker']['port_mappings'][container_port]
                new_host_port = input(f"容器端口 {container_port} -> 主机端口 (当前: {current_host_port}): ").strip()
                if new_host_port and new_host_port.isdigit():
                    config_manager.config['docker']['port_mappings'][container_port] = int(new_host_port)
        
        # 保存配置
        config_manager.save_config()
        print("✅ 配置已保存")
    
    # 创建备份工具实例
    backup_tool = ServerDockerBackupTool(config_manager)
    
    print("\n🚀 开始Docker化备份...")
    print("这将把远程服务器转换为Docker容器")
    
    try:
        success = backup_tool.full_backup()
        
        if success:
            print("\n✅ Docker化备份完成!")
            system_info = backup_tool.collect_source_system_info()
            backup_tool.create_backup_report(system_info)
            print(f"\n📁 备份文件位置: {backup_tool.backup_dir}")
            print("🔧 可以使用生成的Docker镜像运行容器")
        else:
            print("\n❌ Docker化备份失败，请查看日志文件")
            
    except KeyboardInterrupt:
        print("\n\n备份被用户中断")
    except Exception as e:
        print(f"\n❌ 备份过程中发生错误: {e}")
        logger.error(f"备份失败: {e}")

if __name__ == "__main__":
    main()
